#include <GameEngine/Maps/terrain_descriptor.h>



#ifdef GAMEENGINE_NAMESPACES
namespace GameEngine {
#endif


//! Default constructor
TerrainDescriptor::TerrainDescriptor() : displacement_types_(Displacement::Walkable) {
	for ( int k = 0; k < DisplacementTypeCount; ++k ) {
		speeds_[k] = 0.0f;
	}
}

//! Copy constructor
/*!
 @param rhs The terrain descriptor to copy
 */
TerrainDescriptor::TerrainDescriptor(const TerrainDescriptor& rhs) : displacement_types_(rhs.displacement_types_) {
	for ( int k = 0; k < DisplacementTypeCount; ++k ) {
		speeds_[k] = rhs.speeds_[k];
	}
}

//! Destructor
TerrainDescriptor::~TerrainDescriptor() {}

//! Assignment operator
/*!
 @param rhs The terrain descriptor to copy
 */
TerrainDescriptor& TerrainDescriptor::operator=(const TerrainDescriptor& rhs) {
	if ( &rhs != this ) {
		displacement_types_ = rhs.displacement_types_;
		for ( int k = 0; k < DisplacementTypeCount; ++k ) {
			speeds_[k] = rhs.speeds_[k];
		}
	}
	return *this;
}

//! Accessor to the types of displacement allowed on the cell (read only)
/*!
 @return A constant reference to the types of displacement allowed on the cell
 */
const Displacement::DisplacementTypes& TerrainDescriptor::displacement_types() const {
	return displacement_types_;
}

//! Add the given displacement type to the terrain description
/*!
 @param displacement_type The displacement type to add
 @return True if displacement type could be added, false otherwise. Displacement type
         may not be added if it is already allowed on the terrain
 @note If the function succeeds, make sure you also update the displacement speed on this
       type of displacement (default speed is 0, i.e., the units entering this terrain
	   would be stuck there forever)
 */
bool TerrainDescriptor::add_displacement_type(Displacement::DisplacementType displacement_type) {
	if ( has_displacement_type(displacement_type) ) return false;
	displacement_types_ |= displacement_type;
	return true;
}

//! Remove the given displacement type from the terrain description
/*!
 @param displacement_type The displacement type to remove
 @return True if displacement type could be removed, false otherwise. Displacement type
         may not be removed if it is not already allowed on the terrain
 */
bool TerrainDescriptor::remove_displacement_type(Displacement::DisplacementType displacement_type) {
	if ( has_displacement_type(displacement_type) == false ) return false;
	displacement_types_ ^= displacement_type;
	return true;
}

//! Check in the given displacement type if allowed on the terrain
/*!
 @param displacement_type The displacement type to test
 @return True if displacement type is allowed, false otherwise
 @note Displacement types may be allowed even though their associated speed is 0
 */
bool TerrainDescriptor::has_displacement_type(Displacement::DisplacementType displacement_type) const {
	return displacement_types_.testFlag(displacement_type);
}

//! Change the speed associated to the given displacement type
/*!
 @param displacement_type The displacement type whose speed should change
 @param speed The new speed associated to the displacement type
 */
void TerrainDescriptor::set_speed(Displacement::DisplacementType displacement_type, float speed) {
	speeds_[Displacement::displacement_to_index(displacement_type)] = speed;
}

//! Get the speed associated to a specific displacement type
/*!
 @param displacement_type The displacement type to find the speed of
 @return The speed associated to the displacement type. This speed is 0 if displacement type is not
         allowed on this terrain
 */
float TerrainDescriptor::speed(Displacement::DisplacementType displacement_type) const {
	if ( has_displacement_type(displacement_type) == false ) return 0;
	return speeds_[Displacement::displacement_to_index(displacement_type)];
}

//! Get the speed associated to a set of displacement types
/*!
 @param displacement_type The displacement types to find the speed of
 @return The speed associated to the displacement types. This speed is the largest of all the speeds
         allowed both on this terrain and on the parameter displacement_type
 */
float TerrainDescriptor::speed(Displacement::DisplacementTypes displacement_type) const {
	float res = 0;

	// Iterate over all types of displacement
	for ( DisplacementTypesIterator it(displacement_type); it.more(); it.next() ) {
		// Convert index to a displacement type
		Displacement::DisplacementType current_type = it.cur();

		// Get displacement speed and update maximum speed if necessary
		float tmp_speed = speed(current_type);
		if ( res < tmp_speed ) res = tmp_speed;
	}
	return res;
}


//=================================================================================


//! Default constructor
TerrainManager::TerrainManager() {}

//! Copy constructor
/*!
 @param rhs The terrain manager to copy
 */
TerrainManager::TerrainManager(const TerrainManager& rhs) : terrains_(rhs.terrains_), default_terrain_(rhs.default_terrain_) {}

//! Assignment operator
/*!
 @param rhs The terrain manager to copy
 @return A reference to this terrain manager
 */
TerrainManager& TerrainManager::operator=(const TerrainManager& rhs) {
	if ( &rhs != this ) {
		terrains_ = rhs.terrains_;
		default_terrain_ = rhs.default_terrain_;
	}
	return *this;
}

//! Desctructor
TerrainManager::~TerrainManager() {}

//! Add a type of terrain to the terrain manager
/*!
 @param type The type of the terrain to be added
 @param terrain The description of the terrain to be added
 @return True if terrain could be added, false otherwise. Terrains may not be added if
         they already belong to the terrain manager
 */
bool TerrainManager::add_terrain(TerrainDescriptor::TerrainType type, const TerrainDescriptor& terrain) {
	// Check if terrain already exists
	if ( find_terrain(type) == true ) return false;

	// Add terrain
	terrains_.insert( std::pair<TerrainDescriptor::TerrainType,TerrainDescriptor>(type, terrain) );
	return true;
}

//! Remove a type of terrain from the terrain manager
/*!
 @param type The type of the terrain to be removed
 @return True if terrain could be removed, false otherwise. Terrains may not be removed
         if they do not already belong to the terrain manager
 */
bool TerrainManager::remove_terrain(TerrainDescriptor::TerrainType type) {
	// Check if terrain already exists
	if ( find_terrain(type) == false ) return false;

	// Remove terrain
	return ( terrains_.erase(type) > 0 );
}

//! Find a type of terrain in the terrain manager
/*!
 @param type The type of the terrain to be found
 @return True if terrain could be found, false otherwise
 */
bool TerrainManager::find_terrain(TerrainDescriptor::TerrainType type) const {
	return ( terrains_.find(type) != terrains_.end() );
}

//! Accessor to the default terrain description (read-write)
/*!
 @return A reference to the default terrain description
 */
TerrainDescriptor& TerrainManager::default_terrain() {
	return default_terrain_;
}

//! Accessor to the default terrain description (read only)
/*!
 @return A constant reference to the default terrain description
 */
const TerrainDescriptor& TerrainManager::default_terrain() const {
	return default_terrain_;
}

//! Accessor to the list of terrains present in the terrain manager (read only)
/*!
 @return A constant reference to the list of terrains present in the terrain manager
 */
const std::map<TerrainDescriptor::TerrainType, TerrainDescriptor>& TerrainManager::terrains() const {
	return terrains_;
}

//! Accessor to the description associated to the given terrain type
/*!
 @param type The type of the terrain to be found
 @return A constant reference to the description associated to the given terrain type
 */
const TerrainDescriptor& TerrainManager::get_terrain(TerrainDescriptor::TerrainType type) const {
	// Find the terrain in the list of terrains
	std::map<TerrainDescriptor::TerrainType, TerrainDescriptor>::const_iterator it = terrains_.find(type);

	// If there is no such terrain, return the default description
	if ( it == terrains_.end() ) return default_terrain_;

	// Else, return the terrain description
	return it->second;
}


#ifdef GAMEENGINE_NAMESPACES
} // namespace GameEngine
#endif
